//
//  BrowserWebView.m
//  CLBrowser
//
//  Created by 梁慧聪 on 2016/10/4.
//  Copyright © 2021年 梁慧聪. All rights reserved.
//

#import "CLWebView.h"
#import "WebViewHeader.h"
#import "CLHttpHelper.h"
#import "CLWebViewManager.h"
#import "CLDelegateManager+WebViewDelegate.h"
#import "CLMenuHelper.h"
#import "CLWebViewBackForwardList.h"

#import <objc/runtime.h>
#import <objc/message.h>
#import "CLExtentionsManager.h"
#import "NSURL+CLUtility.h"
@interface CLWebView () <MenuHelperInterface,WKScriptMessageHandler,WKNavigationDelegate>

@property (nonatomic, assign, readwrite) BOOL isMainFrameLoaded;
@property (nonatomic, strong) UIActivityIndicatorView *indicatorView;
@property (nonatomic, strong) UILongPressGestureRecognizer *longPressGestureRecognizer;
@property (nonatomic) WKNavigationAction *navigationAction;
@end

@implementation CLWebView

#pragma mark - Dealloc

- (void)dealloc {
    [_indicatorView removeFromSuperview];
    _indicatorView = nil;
    _longPressGestureRecognizer = nil;
    _webModel = nil;
    [_bridge setWebViewDelegate:nil];
    _bridge = nil;
    _homePage = nil;
    _request = nil;
    self.navigationDelegate = nil;
    self.UIDelegate = nil;
    [self stopLoading];
    [self loadHTMLString:@"" baseURL:nil];
}

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self initializeWebView];
    }
    return self;
}

- (void)initializeWebView {
    self.opaque = NO;
    // UI代理
    self.UIDelegate = self;
    // 导航代理
    self.navigationDelegate = self;
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    // 创建设置对象
    WKPreferences *preference = [[WKPreferences alloc]init];
//    preference.javaScriptEnabled = YES;
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    config.userContentController = wkUController;
    config.preferences = preference;
    config.allowsInlineMediaPlayback = YES;
    config.mediaTypesRequiringUserActionForPlayback = NO;
//    config.scalesPageToFit = YES;
    if (@available(iOS 11.0, *)) {
        self.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
    
    if (@available(iOS 13.0, *)) {
        UIActivityIndicatorView *activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleMedium];
        activityView.hidesWhenStopped = YES;
        
        [self addSubview:activityView];
        
        self.indicatorView = activityView;
    }
    
    UILongPressGestureRecognizer *longPressGesture = [UILongPressGestureRecognizer new];
    self.longPressGestureRecognizer = longPressGesture;
    longPressGesture.delegate = [CLWebViewManager sharedInstance].clContainerView;
    [self addGestureRecognizer:longPressGesture];
    
    WEAK_SELF;
    dispatch_main_safe_async(^{
        CLWebViewBridge *bridge = [CLWebViewBridge bridgeForWebView:weakSelf];
        weakSelf.bridge = bridge;
        [CLExtentionsManager.sharedInstance addCommand:weakSelf bridge:bridge];
        [bridge setWebViewDelegate:weakSelf];
        [CLWebViewBridge enableLogging];
    });
    [self.scrollView pp_addObserverForKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew changeAction:^(id  _Nullable object, NSDictionary<NSKeyValueChangeKey,id> * _Nullable change, NSString * _Nullable keyPath) {
        [weakSelf evaluateJavaScript:@"document.getElementsByTagName('html')[0].innerHTML" completionHandler:^(NSString * _Nullable HTMLSource, NSError * _Nullable error) {
            if (weakSelf && !NULLString(HTMLSource) && (weakSelf.HTMLSource == nil || ![weakSelf.HTMLSource isEqualToString:HTMLSource])) {
                weakSelf.HTMLSource = HTMLSource;
                [[CLDelegateManager sharedInstance] performSelector:@selector(webViewForMainFrameDidChangeHtml:) arguments:@[weakSelf] key:kDelegateManagerWebView];
            }
        }];
    }];
    [self pp_addObserverForKeyPath:@"title" options:NSKeyValueObservingOptionNew changeAction:^(id  _Nullable object, NSDictionary<NSKeyValueChangeKey,id> * _Nullable change, NSString * _Nullable keyPath) {
        [weakSelf clWebView:weakSelf didReceiveTitle:weakSelf.mainFTitle forFrame:weakSelf.navigationAction.targetFrame];
    }];
}

#pragma mark - Menu Action

- (nullable WKNavigation *)loadRequest:(NSURLRequest *)request {
    self.request = request;
    return [super loadRequest:request];
}

- (void)startWithUrl:(NSString *)url {
    if ([[NSURL URLWithString:url] isErrorPageURL]) {
        NSURL *originalUrl = [[NSURL URLWithString:url] originalURLFromErrorURL];
        url = originalUrl.absoluteString;
    }
    
    [self loadRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]]];
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    if (action == @selector(copy:)) {
        return YES;
    }
    if (action == @selector(selectAll:)) {
        return YES;
    }
    return NO;
}



#pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    if ([message.name isEqualToString:@"native_log"]) {
        NSString *msg = message.body;
        NSLog(@"[JS-DEBUG] %@", msg);
    }
}

- (void)setBounds:(CGRect)bounds {
    if (!CGSizeEqualToSize(self.bounds.size, bounds.size)) {
        self.indicatorView.center = CGPointMake(self.bounds.origin.x + bounds.size.width / 2, self.bounds.origin.y + bounds.size.height / 2);
    }
    [super setBounds:bounds];
}

- (NSString *)mainFURL {
    NSAssert([NSThread isMainThread], @"method should called in main thread");
    return self.URL.absoluteString;
}

- (NSString *)mainFTitle {
    NSAssert([NSThread isMainThread], @"method should called in main thread");
    return self.title;
}

- (void)webViewBackForwardListWithCompletion:(CLBackForwardListCompletion)completion {
    NSAssert([NSThread isMainThread], @"method should called in main thread");
    
    CLWebView *webView = [self webView];
    CLWebViewBackForwardList *list = nil;
    WKBackForwardList *backForwardList = webView.backForwardList;
    if (backForwardList) {
        NSArray<WKBackForwardListItem *> *backList = backForwardList.backList;
        NSArray<WKBackForwardListItem *> *forwardList = backForwardList.forwardList;
        WKBackForwardListItem *currentItem = backForwardList.currentItem;
        list = [[CLWebViewBackForwardList alloc] initWithCurrentItem:[self getCurrentItem:currentItem] backList:[self getBackForwardItemsWithArray:backList] forwardList:[self getBackForwardItemsWithArray:forwardList]];
    }
    
    if (completion) {
        completion(list);
    }
}

- (CLWebViewHistoryItem *)getCurrentItem:(WKBackForwardListItem *)item{
    if ([item isKindOfClass:[WKBackForwardListItem class]]) {
        CLWebViewHistoryItem *hisItem = [[CLWebViewHistoryItem alloc] initWithItem:item];
        return hisItem;
    }
    return nil;
}

- (NSArray<CLWebViewHistoryItem *> *)getBackForwardItemsWithArray:(NSArray *)items{
    if (items && items.count > 0) {
        NSMutableArray<CLWebViewHistoryItem *> *historyItems = [NSMutableArray arrayWithCapacity:items.count];
        [items enumerateObjectsUsingBlock:^(WKBackForwardListItem *item, NSUInteger idx, BOOL *stop){
            if ([item isKindOfClass:[WKBackForwardListItem class]]) {
                @autoreleasepool {
                    CLWebViewHistoryItem *hisItem = [[CLWebViewHistoryItem alloc] initWithItem:item];
                    [historyItems addObject:hisItem];
                }
            }
        }];
        return historyItems;
    }
    return nil;
}

// get mainFrame
- (id)mainFrameWithWebView:(id)webView {
    if (self.navigationAction.targetFrame.isMainFrame) {
        return self.navigationAction.targetFrame;
    }
    return nil;
}

// get WebView
- (CLWebView *)webView {
    return self;
}

#pragma mark - UIWebViewDelegate
/// 开始请求服务器并加载页面
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
//- (void)webViewDidStartLoad:(CLWebView *)webView{
    NSLog(@"Live");
    [self webViewForMainFrameDidCommitLoad:self];
    [[CLDelegateManager sharedInstance] performSelector:@selector(webViewDidStartLoad:) arguments:@[webView] key:kDelegateManagerWebView];
    [self.indicatorView startAnimating];
    [self evaluateJavaScript];
}
/// 请求服务器发生错误 (如果是goBack时，当前页面也会回调这个方法，原因是NSURLErrorCancelled取消加载)
- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
    NSLog(@"Live");
//- (void)webView:(CLWebView *)webView didFailLoadWithError:(NSError *)error{
    [[CLDelegateManager sharedInstance] performSelector:@selector(webView:didFailLoadWithError:) arguments:@[webView,error] key:kDelegateManagerWebView];
    [self.indicatorView stopAnimating];
    [self evaluateJavaScript];
}
/// 是否允许页面加载,wkwebview信任https接口
- (void)webView:(CLWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSLog(@"Live");
    BOOL isShouldStart = YES;
    self.navigationAction = navigationAction;
    NSArray<WeakCLBrowserDelegate *> *delegates = [[CLDelegateManager sharedInstance] webViewDelegates];
    
    foreach(delegate, delegates) {
        if ([delegate.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
            isShouldStart = [delegate.delegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
        }
    }
//    if (isShouldStart) {
//        isShouldStart = [webView.bridge shouldStartLoad:navigationAction.request pageUrl:navigationAction.request.URL];
//    }
    if (decisionHandler) {
        decisionHandler(isShouldStart ? WKNavigationActionPolicyAllow : WKNavigationActionPolicyCancel);
    }
}

- (BOOL)webView:(CLWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
    NSLog(@"Live");
    BOOL isShouldStart = YES;
    
    NSArray<WeakCLBrowserDelegate *> *delegates = [[CLDelegateManager sharedInstance] webViewDelegates];
    
    foreach(delegate, delegates) {
        if ([delegate.delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
            isShouldStart = [delegate.delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
            if (!isShouldStart) {
                return isShouldStart;
            }
        }
    }
    
    return isShouldStart;
}
/// 页面渲染完成后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
    NSLog(@"Live");
    [self webViewForMainFrameDidFinishLoad:self];
//- (void)webViewDidFinishLoad:(CLWebView *)webView{
    [[CLDelegateManager sharedInstance] performSelector:@selector(webViewDidFinishLoad:) arguments:@[webView] key:kDelegateManagerWebView];

    [self.indicatorView stopAnimating];
}
/// 页面渲染完成后调用
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
    NSLog(@"Live");
    [self webViewForMainFrameDidFinishLoad:self];
//- (void)webViewDidFinishLoad:(CLWebView *)webView{
    [[CLDelegateManager sharedInstance] performSelector:@selector(webViewDidFinishLoad:) arguments:@[webView] key:kDelegateManagerWebView];
    
    [self.indicatorView stopAnimating];
}
#pragma mark - private method

//得到title回调
- (void)clWebView:(id)sender didReceiveTitle:(id)title forFrame:(id)frame {
    NSLog(@"Live");
    if(![title isKindOfClass:[NSString class]])
        return;

//    if ([self respondsToSelector:@selector(clWebView:didReceiveTitle:forFrame:)]) {
//        ((void(*)(id, SEL, id, id, id)) objc_msgSend)(self, @selector(clWebView:didReceiveTitle:forFrame:), sender, title, frame);
//    }
    
    id mainFrame = [self mainFrameWithWebView:sender];
    
    if(mainFrame == frame)
    {
        [self webView:self gotTitleName:title];
    }
    
    [self.indicatorView stopAnimating];
}

- (void)clWebView:(id)webView resource:(id)resource didReceiveAuthenticationChallenge:(id)challenge fromDataSource:(id)source{
    NSLog(@"Live");
    if ([challenge class] == [NSURLAuthenticationChallenge class] ) {
        NSURLAuthenticationChallenge *urlChallenge = (NSURLAuthenticationChallenge *)challenge;
        
        [[CLDelegateManager sharedInstance] performSelector:@selector(webView:didReceiveAuthenticationChallenge:) arguments:@[self, urlChallenge] key:kDelegateManagerWebView];
    }
    else {
//        if([self respondsToSelector:@selector(clWebView:resource:didReceiveAuthenticationChallenge:fromDataSource:)])
//            ((void(*)(id, SEL, id, id, id, id)) objc_msgSend)(self, @selector(clWebView:resource:didReceiveAuthenticationChallenge:fromDataSource:), webView, resource, challenge, source);
    }
}

//MARK:- decidePolicy method

//new window 回调，现在很多网站已经做移动版适配，很少会使用新窗口打开了
- (void)clWebView:(id)webView decidePolicyForNewWindowAction:(id)actionInformation request:(id)request newFrameName:(id)frameName decisionListener:(id)listener{
    NSLog(@"Live");
//    if ([self respondsToSelector:@selector(clWebView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:)]) {
//        ((void(*)(id, SEL, id, id, id, id, id)) objc_msgSend)(self, @selector(clWebView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:), webView, actionInformation, request, frameName, listener);
//    }
    
    if(![request isKindOfClass:[NSURLRequest class]])
        return;
    
    if(![frameName isKindOfClass:[NSString class]])
        return;
    
}

//navigation 回调,如果想实现点击url新窗口打开可以判断intNaviType是否为WebNavigationTypeLinkClicked
- (void)clWebView:(id)webView decidePolicyForNavigationAction:(id)actionInformation request:(id)request frame:(id)frame decisionListener:(id)listener{
    NSLog(@"Live");
    if(![request isKindOfClass:[NSURLRequest class]])
        return;
    
    NSInteger intNaviType = 0;
    if ([actionInformation isKindOfClass:[NSDictionary class]]) {
        id naviType = [((NSDictionary*)actionInformation) objectForKey:WEB_ACTION_NAVI_TYPE_KEY];
        if([naviType isKindOfClass:[NSNumber class]])
        {
            intNaviType = [(NSNumber*)naviType integerValue];
        }
    }
    
//    if([self respondsToSelector:@selector(clWebView:decidePolicyForNavigationAction:request:frame:decisionListener:)])
//        ((void(*)(id, SEL, id, id, id, id, id)) objc_msgSend)(self, @selector(clWebView:decidePolicyForNavigationAction:request:frame:decisionListener:), webView, actionInformation, request, frame, listener);
}

#pragma mark - main frame load functions
- (void)webViewForMainFrameDidCommitLoad:(CLWebView *)webView{
    NSLog(@"Live");
    self.webModel.url = [self mainFURL];
    self.isMainFrameLoaded = NO;
    
    [[CLDelegateManager sharedInstance] performSelector:@selector(webViewForMainFrameDidCommitLoad:) arguments:@[self] key:kDelegateManagerWebView];
}

//webViewMainFrameDidFinishLoad:
- (void)webViewForMainFrameDidFinishLoad:(CLWebView *)webView{
    NSLog(@"Live");
    self.isMainFrameLoaded = YES;
//    self.webModel.image = [self snapshotForBrowserWebView];
    NSString *key = [NSString stringWithFormat:@"%f%@%@",[[NSDate date] timeIntervalSince1970],self.webModel.url,self.webModel.title];
    if ([self.webModel.url isEqualToString:DEFAULT_CARD_CELL_URL]) {
        key = DEFAULT_CARD_CELL_URL;
    }
    self.webModel.imageKey = key;
    [[CLDelegateManager sharedInstance] performSelector:@selector(webViewForMainFrameDidFinishLoad:) arguments:@[self] key:kDelegateManagerWebView];
}

#pragma mark - replaced method calling

- (void)webView:(CLWebView *)webView gotTitleName:(NSString*)titleName{
    NSLog(@"Live");
    self.webModel.title = titleName;
    [[CLDelegateManager sharedInstance] performSelector:@selector(webView:gotTitleName:) arguments:@[webView,titleName] key:kDelegateManagerWebView];
}

#pragma mark - frame method

- (void)clWebView:(id)sender didStartProvisionalLoadForFrame:(id)frame {
    NSLog(@"Live");
    id mainFrame = [self mainFrameWithWebView:sender];
    
    if(mainFrame == frame)
    {
        [self.indicatorView startAnimating];
    }
    
//    if ([self respondsToSelector:@selector(clWebView:didStartProvisionalLoadForFrame:)]) {
//        ((void(*)(id, SEL, id, id)) objc_msgSend)(self, @selector(clWebView:didStartProvisionalLoadForFrame:), sender, frame);
//    }
}

- (void)evaluateJavaScript {
//    WEAK_SELF;
    [self evaluateJavaScript:@"document.readyState" completionHandler:^(NSString *_Nullable readyState, NSError * _Nullable error) {
        NSLog(@"readyState:%@",readyState);
//        BOOL uninitialized = [readyState isEqualToString:@"uninitialized"];
//        BOOL interactive = [readyState isEqualToString:@"interactive"];
        BOOL complete = [readyState isEqualToString:@"complete"];
        if (complete) {
            
        }
    }];
}

- (void)clWebViewDidUpdateUI {
    [[self class] cancelPreviousPerformRequestsWithTarget:self selector:@selector(didUpdateUI) object:nil];
    [self performSelector:@selector(didUpdateUI) withObject:nil afterDelay:0.5];
}

- (void)didUpdateUI {
    [self.bridge callHandler:@"updateUI" data:nil responseCallback:^(NSString *responseData) {
        JSLog(@"[updateUI]%ld",responseData.length);
    }];
}

//#pragma mark - Initialize
//+ (void)initialize{
//    Class browserClass = [CLWebView class];
//    if (self == browserClass) {
//        @autoreleasepool {
//            MethodSwizzle(browserClass, NSSelectorFromString(WEB_GOT_TITLE), @selector(clWebView:didReceiveTitle:forFrame:));
//
//            MethodSwizzle(browserClass, NSSelectorFromString(WEB_NEW_WINDOW), @selector(clWebView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:));
//
//            MethodSwizzle(browserClass, NSSelectorFromString(WEB_ACTION_NAVIGATION), @selector(clWebView:decidePolicyForNavigationAction:request:frame:decisionListener:));
//
//            MethodSwizzle(browserClass, NSSelectorFromString(MAIN_FRAME_COMMIT_LOAD), @selector(clWebViewMainFrameCommitLoad:));
//
//            MethodSwizzle(browserClass, NSSelectorFromString(MAIN_FRAME_FINISIH_LOAD), @selector(clWebViewMainFrameFinishLoad:));
//
//            MethodSwizzle(browserClass, NSSelectorFromString(FRAME_PROVISIONALLOAD), @selector(clWebView:didStartProvisionalLoadForFrame:));
//            MethodSwizzle(browserClass, NSSelectorFromString(WEB_RECEIVE_AUTHENTICATION_CHALLENGE), @selector(clWebView:resource:didReceiveAuthenticationChallenge:fromDataSource:));
//        }
//    }
//}
/** 开始渲染页面时调用，响应的内容到达主页面的时候响应,刚准备开始渲染页面*/
//- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
//
//}
/** 页面加载出错调用 */
//- (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error {
//
//}
/** 接收到服务器跳转请求即服务重定向时之后调用 */
//- (void)webView:(WKWebView *)webView didReceiveServerRedirectForProvisionalNavigation:(WKNavigation *)navigation {
//
//}
/** 收到服务器响应后，决定是否跳转 */
//- (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler {
//
//}

@end



